<?php

include_once(_CORE_PATH. "mapper_prototype.php");
include_once(_APP_PATH. "business/structure/structure_entity.php");
class StructureMapper extends mapper_prototype
{
	static private $_allNodes = array();
	static private $_allNodesRel = array();
	static private $_allNodesActiveRel = array();
	static private $_allNodesByUrl = array();
	static private $_isDataLoaded = false;
	static private $_cacheActualTime = 0;

	private $_sqlFieldsList = 'cid id, parent_cid parentId, name, active, inmenu inMenu, corder cOrder,
							   url, page_type pageType, blocked, draft, session_id sessionId, is_closed isClosed, fullurl, 
							   UNIX_TIMESTAMP(update_time) updateTime, page_params pageParams, module_params moduleParams';
	
	private $_user;
	private $_getOnlyActive;
	private $_source;
	
	public function __construct(active_user_class $user = null, $getOnlyActive = false)
	{
		parent::__construct('structure');
		$this->_user = $user;
		$this->_getOnlyActive = $getOnlyActive;
		
		$this->_source = MozgSource::get('default');
	}
	
	static public function GetInstance(active_user_class $user = null)
	{
		return new self($user);		
	}
	
	protected function User()
	{ 
		if (!$this->_user) throw new Exception('Юзер не определён');
    	return $this->_user; 
	}	
	
	private function CleanCache()
	{
		self::$_allNodes = array();
		self::$_allNodesByUrl = array();
		self::$_allNodesRel = array();
		self::$_allNodesActiveRel = array();
		self::$_isDataLoaded = false;		
		self::$_cacheActualTime = 0;
	}

    private function GetAllData()
    {
    	if (self::$_isDataLoaded) return;
    	$this->CleanCache();
        $stmt = $this->_source->query("SELECT
											" .$this->_sqlFieldsList. "
										FROM
											%%categories%%
										ORDER BY parentId, cOrder");

        $stmt->setFetchMode(PDO::FETCH_INTO, new StructureEntity);

        foreach ($stmt as $row) 
        {
        	$sRow = clone $row;
            self::$_allNodes[$sRow->GetId()] = $sRow;
            
            if ($sRow->GetDraft()) continue;
              
            self::$_allNodesByUrl[$sRow->GetFullUrl()] = $sRow;
            self::$_allNodesRel[$sRow->GetParentId()][] = $sRow->GetId();
           	if ($sRow->GetActive()) self::$_allNodesActiveRel[$sRow->GetParentId()][] = $sRow->GetId();
        
            if ($sRow->GetUpdateTime() > self::$_cacheActualTime) self::$_cacheActualTime = $sRow->GetUpdateTime(); 
        }

        self::$_isDataLoaded = true;
    }
    
    private function GetTableUpdateTime()
    {
        return (int)$this->_source->query("SELECT UNIX_TIMESTAMP(max(update_time)) FROM %%categories%%")->fetchColumn();    	
    }
    
    private function CheckCacheExpire()
    {
    	$updateTime = $this->GetTableUpdateTime();
		if (!$updateTime || ($updateTime > self::$_cacheActualTime)) $this->CleanCache();     	
    }
    
    private function LockTable(array $tableList)
    {
    	if (!is_array($tableList)) $tableList = array($tableList);
    	$sqlTableList = array();
    	foreach ($tableList as $table)
    	{
			$sqlTableList[] = $table. ' WRITE';    		
    	}
    	$this->_source->exec("LOCK TABLES " .implode(',', $sqlTableList));
    	$this->CheckCacheExpire();    	
    }

    private function UnLockTable()
    {
    	$this->_source->exec("UNLOCK TABLES");    	
    }    
    
    private function CreateFullUrl($url, $parentUrl)
    {
		return (strlen($parentUrl) ? $parentUrl : '').urlencode($url). (strlen($url) ? '/' : '');    	
    }
    
    public function GetById($id)
    {
    	$this->GetAllData();
    	if (isset(self::$_allNodes[$id])) 
    	{
	    	if ($this->_getOnlyActive && !self::$_allNodes[$id]->GetActive()) return false;    		
    		return clone self::$_allNodes[$id];
    	}
    	return false;
    }
    
    public function GetByUrl($url)
    {
    	$this->GetAllData();
    	if (isset(self::$_allNodesByUrl[$url])) 
    	{
	    	if ($this->_getOnlyActive && !self::$_allNodesByUrl[$url]->GetActive()) return false; 
	    	return clone self::$_allNodesByUrl[$url];   		
    	}
    	return false;
    } 

    public function GetIdsByType($pageType)
    {
    	$this->GetAllData();
		$stmt = $this->_source->query("SELECT
												cid
											FROM
												%%categories%%
											WHERE
												page_type = " .(int)$pageType. "	
											ORDER BY parent_cid, corder");

        return $stmt->fetchAll(PDO::FETCH_COLUMN, 0);   	
    }
    
    public function GetParentElementsIds($id)
    {
    	$this->GetAllData();
    	
    	$rootId = $this->GetByUrl('')->GetId();
    	
    	$parentIds = array();
    	while (($element = $this->GetById($id)) && $element->GetParentId() && ($element->GetParentId() != $rootId))
    	{
    		$parentIds[] = 	$element->GetParentId();
    		$id = $element->GetParentId();
    	}
    	
    	return array_reverse($parentIds);
    }
    
    public function GetSubElementsIds($id = 0, $allLevels = false)
    {
    	$this->GetAllData();
        if ($this->_getOnlyActive && isset(self::$_allNodesActiveRel[$id])) $sub = (array)self::$_allNodesActiveRel[$id];
        else if (!$this->_getOnlyActive && isset(self::$_allNodesRel[$id])) $sub = (array)self::$_allNodesRel[$id];
        else $sub = array();
        
        if (!$allLevels) return $sub;
        
        $ssub = $sub;
        for ($i = 0; $i < sizeof($sub); $i++) 
        {
            $nCid = $sub[$i];
			$ssub = array_merge($ssub, $this->GetSubElementsIds($nCid, true));
        }
        return $ssub;
    }

    public function GetDraft()
    {
        $obj = $this->_source->query("SELECT
											   " .$this->_sqlFieldsList. "
						                 FROM %%categories%%
						                 WHERE
						                       draft and
						                       session_id='" . $this->User()->sessionId . "'
										 LIMIT 1")->fetchObject('StructureEntity');

        if (!$obj) 
        {
        	$rootElem = $this->GetByUrl('');
        	if ($rootElem) $parentId = $rootElem->GetId();
        	else $parentId = 0;

	        $this->_source->exec("insert into %%categories%%(name, parent_cid, active, blocked, draft, session_id, is_closed)
					            values(
							             'Новый раздел',
							             '" .$parentId. "',
							             '1',
							             '0',
							             1,
							             '" . $this->User()->sessionId . "',
							             0
						             )
			            ");
	        
	        if ($this->_source->lastInsertID())
	        {
		        $obj = new StructureEntity;
		        $obj->SetId($this->_source->lastInsertID());
		        $obj->SetParentId($parentId);
		        $obj->SetName('Новый раздел');
		        $obj->SetActive(1);
		        $obj->SetBlocked(0);
		        $obj->SetDraft(1);
	        }  	
        }
        
        self::$_allNodes[$obj->GetId()] = $obj;
		        
        return $this->GetById($obj->GetId()); 	
    }
    
    public function Save(StructureEntity $obj)
    {
    	$this->LockTable(array('%%categories%%'));
    	
    	if ($this->Prepare($obj))
    	{
	        $numRows = $this->_source->exec("update %%categories%%
									              set
										              name=" . $this->_source->quote($obj->GetName()) . ",
										              parent_cid='" . $obj->GetParentId() . "',
										              active='" . $obj->GetActive() . "',
										              corder='" . $obj->GetCOrder() . "',
										              url='" . $obj->GetUrl() . "',
										              page_type='" . $obj->GetPageType() . "',
										              inmenu='" . $obj->GetInMenu() . "',
										              blocked='" . $obj->GetBlocked() . "',
										              fullurl=" . $this->_source->quote($obj->GetFullUrl()) . ",
										              draft=0,
										              session_id='',
										              update_time=NOW(),
										              page_params=" .$this->_source->quote((string)$obj->GetPageParams()). ",
										              module_params=" .$this->_source->quote((string)$obj->GetModuleParams()). "
									             where cid='" . $obj->GetId() . "'
									            ");    	
	    
	        if ($numRows) 
	        {	
	        	$realItem = $this->GetById($obj->GetId());
	        	$this->UpdateSubElementsFullUrls($realItem->GetFullUrl(), $obj->GetFullUrl());
	        	$this->CleanCache();
	        	$status = true;
	        }	
	        else 
	        {
	        	$status = false;    
	        }
    	}
    	else 
    	{
			$status = false;    		
    	}
    	        
        $this->UnLockTable();
        
        return $status;
    }
    
    private function UpdateSubElementsFullUrls($oldFullUrl, $newFullUrl)
    {
        if (!strlen($oldFullUrl) || ! strlen($newFullUrl) || ($oldFullUrl == $newFullUrl)) return;
        $this->_source->exec("UPDATE %%categories%%
        								set
                                            fullurl=INSERT(fullurl,1," . strlen($oldFullUrl) . "," . $this->_source->quote($newFullUrl) . ")
        								where
        									fullurl LIKE " . $this->_source->quote($oldFullUrl. "%"));
	} 

    private function GetMaxCorder($parentId)
    {
    	if (!$this->GetById($parentId)) return 0;
        $ids = $this->GetSubElementsIds($parentId);
        $item = $this->GetById(array_pop($ids));
        if (!$item) return 0;
        return $item->GetCOrder();
    }    
    
    private function CheckUrl($id, $fullUrl)
    {
        $elem = $this->GetByUrl($fullUrl);
        return ($elem && ($elem->GetId() != $id));
    }    
    
    protected function TrunslateUrl($url)
    {
        $url = $this->Libs()->str->ToLower($url);
        $iso = array("і" => "i" , "є" => "ye" , "ѓ" => "g" , "а" => "a" , "б" => "b" , "в" => "v" , "г" => "g" , "д" => "d" , "е" => "e" , "ё" => "yo" , "ж" => "zh" , "з" => "z" , "и" => "i" , "й" => "j" , "к" => "k" , "л" => "l" , "м" => "m" , "н" => "n" , "о" => "o" , "п" => "p" , "р" => "r" , "с" => "s" , "т" => "t" , "у" => "u" , "ф" => "f" , "х" => "x" , "ц" => "c" , "ч" => "ch" , "ш" => "sh" , "щ" => "shh" , "ъ" => "" , "ы" => "y" , "ь" => "" , "э" => "e" , "ю" => "yu" , "я" => "ya" , " " => "_");
        $url = strtr($url, $iso);
        return preg_replace('|[^a-z0-9-_]|i', '', $url);
    }    
    
    private function Prepare(StructureEntity $obj)
    {
    	$realItem = $this->GetById($obj->GetId());
    	if (!$realItem) 
    	{
    		$realItem = $this->GetDraft();
    		$obj->SetId($realItem->GetId());
    	}

		if (!strlen($obj->GetName())) return $this->SetErrorMessage('Введите название'); 
		if (is_null(MozgCfg::GetProp('global/structure//pageTypes/' .$obj->GetPageType()))) return $this->SetErrorMessage('Неправильный тип раздела'); 		

		$rootElem = $this->GetByUrl('');		
		if (!$rootElem || ($obj->GetId() == $rootElem->GetId()))
		{
			$obj->SetParentId(0);
			$obj->SetUrl('');
		}
		else 
		{				
			$parentItem = $this->GetById($obj->GetParentId());		
	    	if (!$parentItem) 
	    	{
	    		return $this->SetErrorMessage('Не найден родительский раздел');    		
	    	}
	    		
	        $subItemsIds = $this->GetSubElementsIds($obj->GetId(), true);
	        $subItemsIds[] = $obj->GetId();
	        if (in_array($obj->GetParentId(), $subItemsIds)) 
	        {
	            return $this->SetErrorMessage('Раздел не может принадлежать своим подразделам');
	        } 				
				
			if (!strlen($obj->GetUrl())) $obj->SetUrl($this->TrunslateUrl($obj->GetName()));
			
			if (!preg_match('/^[a-z_\-0-9]+$/uis', $obj->GetUrl())) 
	        {
	            return $this->SetErrorMessage('Неправильный url');
	        }	       
	        
			$parentUrl = $parentItem->GetFullUrl(); 		
			$obj->SetFullUrl($this->CreateFullUrl($obj->GetUrl(), $parentUrl));		
	
			if ($this->CheckUrl($obj->GetId(), $obj->GetFullUrl()))
			{
				return $this->SetErrorMessage('В текущем разделе такой Url уже существует');			
			}	
					
			if ($obj->GetDraft() || ($realItem->GetParentId() != $parentItem->GetId())) 
			{
				$obj->SetCOrder($this->GetMaxCorder($obj->GetParentId()) + 1);
			}			
		}	

		if ($obj->GetDraft()) $obj->SetDraft(0);
		
		return true;
    }

    public function Sort(array $idsToSort)
    {
    	if (!sizeof($idsToSort)) return false;
    	
    	$item = $this->GetById($idsToSort[0]);
    	if (!$item) return false;
    	
        $allItemsIds = $this->GetSubElementsIds($item->GetParentId());
        $idsToSort = array_values(array_intersect($idsToSort, $allItemsIds));
        if (sizeof($idsToSort) < 2) return false;

		$this->_source->exec("SET @pos = 0");
		$this->_source->exec("update %%categories%%
										      set
									              corder = (SELECT @pos := @pos + 1)
										      where 
										      	 parent_cid=" .$item->GetParentId(). "
										      ORDER BY FIELD(cid, " .implode(',', $idsToSort). ")	 	 
									     ");        
        
        return true;
    }    
    
    public function GetStaticContent($id)
    {
        return $this->_source->query("SELECT
											   content_body
						                 FROM %%categories%%
						                 WHERE
						                       cid='" . (int)$id . "'")->fetchColumn();        
    }    
    
    public function SaveStaticContent($id, $content)
    {
        $content = $this->ClearText($content);
        
        $this->LockTable(array('%%categories%%'));
        $item = $this->GetById($id);
        if (!$item) $item = $this->GetDraft();
        $num = $this->_source->exec("UPDATE %%categories%%
												SET
													content_body = " . $this->_source->quote($this->Libs()->str->EscapeAbsoluteUrls($content)) . "
												WHERE
													cid='" . $item->GetId() . "'");

        $this->UnLockTable();
        return (bool)$num;
    }    
    
    public function Delete($id)
    {
        $item = $this->GetById($id);
        if (!$item) return false; 

        $rootElem = $this->GetByUrl('');
        if ($rootElem && ($id == $rootElem->GetId()))
        {
			$this->_source->exec("TRUNCATE TABLE %%categories%%");		
			$this->CleanCache();
			return true;        		
        }        

        $num = $this->_source->exec("delete 
        										cats
        									from 
        										%%categories%% cats 
        									where 
        										cats.fullurl LIKE " . $this->_source->quote($item->GetFullUrl(). "%"). " and 
        										!cats.draft");

        if ($num)
        {
	        $this->_source->exec("UPDATE %%categories%% SET update_time = NOW() WHERE cid='" . $item->GetParentId() . "' and !draft");        	
        	$this->CleanCache();
			return true;
        }
        
        return false;
    }    
    
    public function GetContentAccess($id)
    {
        return $this->_source->query("SELECT
												group_id
											FROM
												%%content_access%%
											WHERE
												cid = '" . (int)$id . "'")->fetchAll(PDO::FETCH_COLUMN);
    }
    public function SaveContentAccess($id, $isClosed)
    {
        $num = $this->_source->exec("UPDATE %%categories%%
								SET
									is_closed = '" . (int)$isClosed . "'
								WHERE
									cid='" . (int)$id . "'");

        if ($num) $this->CleanCache();
        return true;
    }    
    
    private function ClearText($text)
    {
        return $this->Libs()->minifier->MinifyHTML($this->Libs()->purifier->purifyHTML($text));
    }

    public function ClearDrafts()
    {
        $this->_source->exec("DELETE
		        						cats
			        				FROM
						                %%categories%% cats 
						                		left join %%session_data%% sd on(cats.session_id = sd.session_id)
						            WHERE
						                cats.draft and
						                sd.session_id is NULL");
    } 

    public function __destruct()
    {
    	if (mt_rand(1, 50) == 1) $this->ClearDrafts(); 
    }
}